/**
    @author: ChenLin
    @date: 2022/11/11 todo dfs上传工具类
**/

package handler

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"flamingo/logger"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	//"net/url"
	"bytes"
	"go/XXProtocols"
	"mime/multipart"
	"net/textproto"
	"strconv"
	"strings"
)

/*
 * 上传文件的DFS类   参考：FlamingoDFS.class.php
 *  From Wiki：http://wiki.corp.flamingo-inc.com/pages/viewpage.action?pageId=11862882
 */

type FlamingoDFS struct {
	appId        string
	appSecret    string
	hostUpload   string
	hostUpdate   string
	hostCheck    string
	hostCheckUrl string
}

type UploadData struct {
	Name     string
	Data     []byte
	FileType int32
	Md5      string
}

type UploadResult struct {
	Result  bool   `json:"result"`
	Code    int    `json:"code"`
	Message string `json:"message"`
	Md5     string `json:"md5"`
}

func NewUploadResult() *UploadData {
	return &UploadData{}
}

/*
 * appid        业务ID,通过申请获取
 * appSecret    业务密码,通过申请获取
 * host_upload  上传文件的地址
 * host_update  更新文件的地址
 * host_check   检查文件是否存在的地址
 */
func (self *FlamingoDFS) Init(appId string, appSecret string, hostUpload string, hostUpdate string, hostCheck string, hostCheckUrl string) {
	self.appId = appId
	self.appSecret = appSecret
	self.hostUpload = hostUpload
	self.hostUpdate = hostUpdate
	self.hostCheck = hostCheck
	self.hostCheckUrl = hostCheckUrl
}

var dfs interface{} = nil

func NewFlamingoDFS() *FlamingoDFS {
	if dfs == nil {
		dfs = &FlamingoDFS{}
	}
	return dfs.(*FlamingoDFS)
}

func (self *FlamingoDFS) addHeader(r *http.Request, header map[string]string) {
	for k, v := range header {
		r.Header.Add(k, v)
	}
}

//  将params转换成url的形式
func (self *FlamingoDFS) httpBuildQuery(params map[string]string) string {
	params_arr := make([]string, 0, len(params))
	for k, v := range params {
		params_arr = append(params_arr, fmt.Sprintf("%s=%s", k, v))
	}
	param_str := strings.Join(params_arr, "&")
	return param_str
}

// MIME 协议
func (self *FlamingoDFS) getMimeHeader(fileInfo *UploadData) *textproto.MIMEHeader {
	mimeHeader := make(textproto.MIMEHeader)
	mimeHeader.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, "file", fileInfo.Name))

	switch fileInfo.FileType {

	case int32(XXProto.FileType_FT_JPG):
		mimeHeader.Set("Content-Type", "image/jpeg")

	case int32(XXProto.FileType_FT_PNG):
		mimeHeader.Set("Content-Type", "image/png")

	case int32(XXProto.FileType_FT_None):
		mimeHeader.Set("Content-Type", "text/plain")
	case int32(XXProto.FileType_FT_MP4):
		mimeHeader.Set("Content-type", "video/mp4")
	default:
		logger.Logln(logger.ERROR, "Unknown File Type")
		return nil
	}
	return &mimeHeader
}

/*
 * 发送请求的底层接口
 * follow  0不设置，1设置，设置之后启用时会将服务器服务器返回的"Location: "放在header中递归的返回给服务器(一般不理)
 */
func (self *FlamingoDFS) MakeRequest(uri string, argument map[string]string, method string, cookie string, follow int, fileInfo UploadData) *UploadResult {
	if uri == "" {
		logger.Logln(logger.ERROR, "Empty Uri")
		return nil
	}
	if !strings.Contains(uri, "http://") && !strings.Contains(uri, "https://") {
		logger.Logln(logger.ERROR, "Error Uri")
		return nil
	}
	// http header 相关
	header := map[string]string{
		"Accept-Language": "zh-cn,zh;q=0.8",
		"Connection":      "Keep-alive",
		"Cache-Control":   "max-age=0",
		"Accept-Encoding": "gzip",
	}

	client := &http.Client{}
	var req *http.Request
	var err error

	if method == "POST" && len(argument) > 0 {
		mimeHeader := self.getMimeHeader(&fileInfo)
		body := &bytes.Buffer{}
		data := bytes.NewReader(fileInfo.Data)
		writer := multipart.NewWriter(body)
		part, err := writer.CreatePart(*mimeHeader)
		if err != nil {
			logger.Logln(logger.ERROR, "Create Part fail")
			return nil
		}
		_, err = io.Copy(part, data)
		for k, v := range argument {
			_ = writer.WriteField(k, v)
		}
		err = writer.Close()
		if err != nil {
			logger.Logln(logger.ERROR, "Close fail")
			return nil
		}
		req, err = http.NewRequest("POST", uri, body)
		req.Header.Set("Content-type", string("multipart/form-data; boundary=")+writer.Boundary())
		if err != nil {
			logger.Logln(logger.ERROR, "Create Post request fail")
		}
	} else if method == "GET" && len(argument) > 0 {
		uri = uri + string("?") + self.httpBuildQuery(argument)
		req, err = http.NewRequest("GET", uri, nil)
		if err != nil {
			logger.Logln(logger.ERROR, "Create Get request fail")
		}
	}
	self.addHeader(req, header)
	resp, err := client.Do(req)
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	result := UploadResult{}
	err = json.Unmarshal(body, &result)
	if err != nil {
		logger.Logln(logger.ERROR, err)
	}
	logger.Logln(logger.DEBUG, result)
	return &result
}

func (self *FlamingoDFS) MakeRequestV1(uri string, argument map[string]string, method string, cookie string, follow int) *UploadResult {
	if uri == "" {
		logger.Logln(logger.ERROR, "Empty Uri")
		return nil
	}
	if !strings.Contains(uri, "http://") && !strings.Contains(uri, "https://") {
		logger.Logln(logger.ERROR, "Error Uri")
		return nil
	}
	// http header 相关
	header := map[string]string{
		"Accept-Language": "zh-cn,zh;q=0.8",
		"Connection":      "Keep-alive",
		"Cache-Control":   "max-age=0",
		"Accept-Encoding": "gzip",
	}

	client := &http.Client{}
	var req *http.Request
	var err error

	if method == "POST" && len(argument) > 0 {
		//mimeHeader := self.getMimeHeader(&fileInfo)
		body := &bytes.Buffer{}
		//data := bytes.NewReader(fileInfo.data)
		writer := multipart.NewWriter(body)
		//part, err := writer.CreatePart(*mimeHeader)
		if err != nil {
			logger.Logln(logger.ERROR, "Create Part fail")
			return nil
		}
		//_, err = io.Copy(part, data)
		for k, v := range argument {
			_ = writer.WriteField(k, v)
		}
		err = writer.Close()
		if err != nil {
			logger.Logln(logger.ERROR, "Close fail")
			return nil
		}
		req, err = http.NewRequest("POST", uri, body)
		req.Header.Set("Content-type", string("multipart/form-data; boundary=")+writer.Boundary())
		if err != nil {
			logger.Logln(logger.ERROR, "Create Post request fail")
		}
	} else if method == "GET" && len(argument) > 0 {
		uri = uri + string("?") + self.httpBuildQuery(argument)
		req, err = http.NewRequest("GET", uri, nil)
		if err != nil {
			logger.Logln(logger.ERROR, "Create Get request fail")
		}
	}
	self.addHeader(req, header)
	resp, err := client.Do(req)
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	result := UploadResult{}
	err = json.Unmarshal(body, &result)
	if err != nil {
		logger.Logln(logger.ERROR, err)
	}
	logger.Logln(logger.DEBUG, result)
	return &result
}

/*
 *  上传文件接口
 *  uri         URI地址，业务链接的相对地址，没有填写则自动根据文件类型生成
 *  dfsSaveName 用户下载时保存的文件名，没有则用上传到服务器的文件名，可能存在重命名的情况，建议传参
 *  desc        文件描述
 *  precache    是否推送CDN，true是，false否，默认是true  (precache是让cdn主动来拉取这个url资源并缓存)
 *  refcache    刷新缓存(Cache层),true是，false否，默认为true(将已经缓存在dns的数据清理掉)
 *  canUpdate   文件是否后续可更新，true可以更新，false不可以更新，默认为true
 */

func (self *FlamingoDFS) UploadFileToDFS(fileInfo UploadData, uri string, dfsSaveName string, appZone string, desc string, precache bool, refcache bool, canUpdate bool) *UploadResult {
	argument := map[string]string{
		"appid":     self.appId,
		"appsecret": self.appSecret,
		"precache":  strconv.FormatBool(precache),
		"refcache":  strconv.FormatBool(refcache),
		"canupdate": strconv.FormatBool(canUpdate),
	}
	if dfsSaveName != "" {
		argument["filename"] = dfsSaveName
	}
	if desc != "" {
		argument["description"] = desc
	}
	if uri != "" {
		argument["uri"] = uri
	}
	if appZone != "" {
		argument["appzone"] = appZone
	}
	response := self.MakeRequest(self.hostUpload, argument, "POST", "", 0, fileInfo)
	return response
}

func (self *FlamingoDFS) UpdateFileToDFS(fileInfo UploadData, uri string, dfsSaveName string, appZone string, desc string, precache bool, refcache bool, canUpdate bool) *UploadResult {
	argument := map[string]string{
		"appid":     self.appId,
		"appsecret": self.appSecret,
		"precache":  strconv.FormatBool(precache),
		"refcache":  strconv.FormatBool(refcache),
		"canupdate": strconv.FormatBool(canUpdate),
	}
	if dfsSaveName != "" {
		argument["filename"] = dfsSaveName
	}
	if desc != "" {
		argument["description"] = desc
	}
	if uri != "" {
		argument["uri"] = uri
	}
	if appZone != "" {
		argument["appzone"] = appZone
	}
	response := self.MakeRequest(self.hostUpdate, argument, "POST", "", 0, fileInfo)
	return response
}
func (self *FlamingoDFS) UploadFileToDFSV1(fileInfo string, uri string, dfsSaveName string, appZone string, desc string, precache bool, refcache bool, canUpdate bool) *UploadResult {
	argument := map[string]string{
		"appid":     self.appId,
		"appsecret": self.appSecret,
		"precache":  strconv.FormatBool(precache),
		"refcache":  strconv.FormatBool(refcache),
		"canupdate": strconv.FormatBool(canUpdate),
	}
	if dfsSaveName != "" {
		argument["filename"] = dfsSaveName
	}
	if desc != "" {
		argument["description"] = desc
	}
	if uri != "" {
		argument["uri"] = uri
	}
	if appZone != "" {
		argument["appzone"] = appZone
	}
	if fileInfo != "" {
		argument["file"] = "@" + fileInfo
	}
	response := self.MakeRequestV1(self.hostUpload, argument, "POST", "", 0)
	return response
}

/*
 *  根据MD5检查文件是否存在
 */
func (self *FlamingoDFS) CheckFileExistByMd5(fileInfo UploadData, ttl int) *UploadResult {
	if fileInfo.Md5 == "" {
		return nil
	}
	argument := map[string]string{
		"appid": self.appId,
		"md5":   fileInfo.Md5,
	}
	response := self.MakeRequest(self.hostCheck, argument, "GET", "", 0, fileInfo)
	return response
}

/*
 *  上传文件之前根据MD5检查文件是否存在再上传
 */
func (self *FlamingoDFS) CheckImageExist(fileObject *XXProto.FileObject) string {
	md5Num := md5.New()
	md5Num.Write(fileObject.FileBytes)
	checkSum := hex.EncodeToString(md5Num.Sum(nil))
	return checkSum
}
